#ifndef AER_BLOOMEFFECT_HPP_
#define AER_BLOOMEFFECT_HPP_

#include "aerPostEffect.hpp"
#include "aerBlurEffect.hpp"


namespace aer {
  
class BloomEffect : public PostEffect
{
  private:
    struct Pass
    {
      Program program;
      RenderTexture rt;
      Texture color;
    };
    
    struct Input
    {
      Texture *pColor;
      Texture *pBlurred;
    };
    
    
  private:    
    Pass m_brightPass;
    Pass m_compoPass;
    BlurEffect m_blur;
    
    Input m_input;
    
    float m_threshold;
    float m_bloomFactor;
    
    
  public:
    BloomEffect()
    {
      /// Note: alternatively, create 2 sub-effects (Bright & Compo).
      
      // Bloom Input
      addInput( "Color", m_input.pColor);      
      
      // Blur input
      m_blur.linkInput( "Color", &m_brightPass.color);
      
      // Blur output / Compo input
      m_input.pBlurred = m_blur.getOutput<Texture>( "Color" );
      
      // Bloom Output
      addOutput( "Color", m_compoPass.color);
      
      
      m_blur.setKernelSize( 8 );
      m_threshold = 0.50f;
      m_bloomFactor = 1.0f;
    }
    
    ~BloomEffect()
    {
      m_brightPass.program.release();
      m_brightPass.rt.release();
      m_brightPass.color.release();
      
      m_compoPass.program.release();
      m_compoPass.rt.release();
      m_compoPass.color.release();
    }
    
    
    void init()
    {
      const char *bloomFS = AER_STRINGIFY(
        \x23 version 330\n
        
        in vec2 vTexCoord;
        layout(location = 0) out vec4 fragColor;
        uniform sampler2D uInputTex;
        uniform float uThreshold = 0.15f;

        void main()
        {
          vec4 inColor = texture( uInputTex, vTexCoord);
          
          // Compute a 'standard' greyscale for luminance
          float grey = dot( vec3(0.299f, 0.587f, 0.114f), inColor.rgb);
          
          fragColor.r = step( uThreshold, grey);
        }
      );
      
      m_brightPass.program.generate();
        m_brightPass.program.addShaderSrc( Program::VERTEX_SHADER, ScreenQuad::GetVertexShader());
        m_brightPass.program.addShaderSrc( Program::FRAGMENT_SHADER, bloomFS);
      AER_CHECK( m_brightPass.program.link() );
      
      
      const char *compoFS = AER_STRINGIFY(
        \x23 version 330\n
        
        in vec2 vTexCoord;
        layout(location = 0) out vec4 fragColor;
        uniform sampler2D uInputTex;
        uniform sampler2D uBloomTex;
        uniform float uBloomFactor = 1.0f;

        void main()
        {
          vec4 inColor = texture( uInputTex, vTexCoord);
          float bloomCoeff = texture( uBloomTex, vTexCoord).r;
          
          fragColor.rgb = inColor.rgb * vec3(1.0f + bloomCoeff * uBloomFactor);
          fragColor.a = inColor.a;
        }
      );
      
      m_compoPass.program.generate();
        m_compoPass.program.addShaderSrc( Program::VERTEX_SHADER, ScreenQuad::GetVertexShader());
        m_compoPass.program.addShaderSrc( Program::FRAGMENT_SHADER, compoFS);
      AER_CHECK( m_compoPass.program.link() );
      
      m_brightPass.color.generate();
      m_compoPass.color.generate();
      
      m_brightPass.color.bind();    
        m_brightPass.color.setFilters( Texture::NEAREST, Texture::NEAREST);
        m_brightPass.color.setWraps( Texture::CLAMP_TO_EDGE, Texture::CLAMP_TO_EDGE);
      m_compoPass.color.bind();    
        m_compoPass.color.setFilters( Texture::NEAREST, Texture::NEAREST);
        m_compoPass.color.setWraps( Texture::CLAMP_TO_EDGE, Texture::CLAMP_TO_EDGE);
      Texture::Unbind( Texture::TEXTURE_2D, 0u);
      
      m_brightPass.rt.generate();
      m_compoPass.rt.generate();
      
            
      m_blur.init();
    }
    
    void resize(const U32 width, const U32 height)
    {
      // Downsample the image to blur
      U32 subWidth = width >> 2;
      U32 subHeight = height >> 2;
      
      m_brightPass.color.bind();
      m_brightPass.color.resize( subWidth, subHeight, GL_RED);
      m_compoPass.color.bind();
      m_compoPass.color.resize( width, height, GL_RGBA8);
      Texture::Unbind( Texture::TEXTURE_2D, 0u);

      m_brightPass.rt.bind();
      AER_CHECK( m_brightPass.rt.setup( &m_brightPass.color ) );
      m_compoPass.rt.bind();
      AER_CHECK( m_compoPass.rt.setup( &m_compoPass.color ) );
      RenderTarget::unbind();
            
      m_brightPass.rt.resize( subWidth, subHeight);  //
      m_compoPass.rt.resize( width, height);  //
      
      
      m_blur.resize( subWidth, subHeight);
    }
    
    void apply()
    {
      // Downsample BRIGHT + BLUR pass
      glViewport( 0, 0, m_brightPass.color.getWidth(), m_brightPass.color.getHeight());//
      
      /// 1) brighting pass
      m_brightPass.rt.begin();
      m_brightPass.program.bind();      
        m_brightPass.program.setUniform( "uInputTex", 0);
        m_brightPass.program.setUniform( "uThreshold", m_threshold);
        m_input.pColor->bind( 0u );
          ScreenQuad::Draw();
      m_brightPass.program.unbind();
      m_brightPass.rt.end();
      
      
      /// 2) Horizontal & Vertical blur passes
      // note : BlurEffect use a RGBA texture wheres it could use only RED for the brightness.
      m_blur.apply();
      
      
      glViewport( 0, 0, m_compoPass.color.getWidth(), m_compoPass.color.getHeight());//
      
      /// 3) Composition pass
      if (getTarget() == TEXTURE) { m_compoPass.rt.begin(); }
      m_compoPass.program.bind();
        m_compoPass.program.setUniform( "uBloomFactor", m_bloomFactor);
        m_compoPass.program.setUniform( "uInputTex", 0);
        m_compoPass.program.setUniform( "uBloomTex", 1);
        m_input.pColor->bind( 0u );  
        m_input.pBlurred->bind( 1u );  
          ScreenQuad::Draw();      
      m_compoPass.program.unbind();
      if (getTarget() == TEXTURE) { m_compoPass.rt.end(); }
      
      Texture::Unbind( Texture::TEXTURE_2D, 1u);
      Texture::Unbind( Texture::TEXTURE_2D, 0u);
      
      
      CHECKGLERROR();
    }
        
    
    float getThreshold() const { return m_threshold; }
    float getBloomFactor() const { return m_bloomFactor; }
    
    void setThreshold(const float threshold) { m_threshold = threshold; }
    void setBloomFactor(const float factor) { m_bloomFactor = factor; }    
};
  
} // aer

#endif //AER_BLOOMEFFECT_HPP_
